#include <vtkActor.h>
#include <vtkBox.h>
#include <vtkCamera.h>
#include <vtkColorSeries.h>
#include <vtkContourFilter.h>
#include <vtkImplicitBoolean.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSampleFunction.h>
#include <vtkSphere.h>


int main(int /* argc */, char*[])
{
    vtkNew<vtkNamedColors> colors;

    // create a sphere
    vtkNew<vtkSphere> sphere;
    sphere->SetRadius(1);
    sphere->SetCenter(1, 0, 0);

    // create a box
    vtkNew<vtkBox> box;
    box->SetBounds(-1, 1, -1, 1, -1, 1);

    // combine the two implicit functions
    vtkNew<vtkImplicitBoolean> boolean;
    boolean->AddFunction(box);
    boolean->AddFunction(sphere);

    std::vector<vtkNew<vtkRenderer>> ren;
    ren.push_back(vtkNew<vtkRenderer>());
    ren.push_back(vtkNew<vtkRenderer>());
    ren.push_back(vtkNew<vtkRenderer>());
    ren[0]->SetViewport(0, 0, 1.0 / 3.0, 1);         // Difference
    ren[1]->SetViewport(1.0 / 3.0, 0, 2.0 / 3.0, 1); // Union
    ren[2]->SetViewport(2.0 / 3.0, 0, 1, 1);         // Intersection

    // Shared camera
    vtkNew<vtkCamera> camera;
    camera->Azimuth(30.0);
    camera->Elevation(30.0);

    vtkNew<vtkColorSeries> colorSeries;
    colorSeries->SetColorScheme(vtkColorSeries::BREWER_DIVERGING_SPECTRAL_3);

    for (int i = 0; i < 3; ++i)
    {
        if (i == 0)
        {
            boolean->SetOperationTypeToDifference();
        }
        else if (i == 1)
        {
            boolean->SetOperationTypeToUnion();
        }
        else
        {
            boolean->SetOperationTypeToIntersection();
        }

        // The sample function generates a distance function from the implicit
        // function. This is then contoured to get a polygonal surface.
        vtkNew<vtkSampleFunction> sample;
        sample->SetImplicitFunction(boolean);
        sample->SetModelBounds(-1, 2, -1, 1, -1, 1);
        sample->SetSampleDimensions(100, 100, 100);
        sample->ComputeNormalsOff();

        // contour
        vtkNew<vtkContourFilter> surface;
        surface->SetInputConnection(sample->GetOutputPort());
        surface->SetValue(0, 0.0);
        surface->Update();

        vtkNew<vtkPolyData> polyData;
        polyData->DeepCopy(surface->GetOutput());

        // mapper
        vtkNew<vtkPolyDataMapper> mapper;
        mapper->SetInputData(polyData);
        mapper->ScalarVisibilityOff();

        vtkNew<vtkActor> actor;
        actor->SetMapper(mapper);
        //    actor->GetProperty()->EdgeVisibilityOn();
        actor->GetProperty()->SetColor(colors->GetColor3d("peacock").GetData());

        // add the actor
        ren[i]->SetBackground(colorSeries->GetColor(i).GetRed() / 255.0,
                              colorSeries->GetColor(i).GetGreen() / 255.0,
                              colorSeries->GetColor(i).GetBlue() / 255.0);
        ren[i]->SetActiveCamera(camera);
        ren[i]->AddActor(actor);
    }

    // render window
    vtkNew<vtkRenderWindow> renwin;
    renwin->AddRenderer(ren[0]);
    renwin->AddRenderer(ren[1]);
    renwin->AddRenderer(ren[2]);

    // An interactor
    vtkNew<vtkRenderWindowInteractor> interactor;
    interactor->SetRenderWindow(renwin);

    renwin->SetSize(900, 300);
    renwin->SetWindowName("ImplicitBooleanDemo");
    renwin->Render();
    ren[1]->ResetCamera();
    renwin->Render();

    // Start
    interactor->Initialize();
    interactor->Start();

    return EXIT_SUCCESS;
}
